package com.zycfc.zsf.boot.util.bean;

import java.lang.annotation.*;
import java.lang.reflect.*;
import org.apache.commons.lang3.*;
import java.util.*;

public abstract class Reflections
{
    public static boolean isVoid(final Class<?> clazz) {
        return clazz == Void.TYPE || clazz == Void.class;
    }
    
    public static Object getProperty(final Object target, final String name) throws Exception {
        final Field f = target.getClass().getDeclaredField(name);
        f.setAccessible(true);
        return f.get(target);
    }
    
    public static Object getFieldValue(final Object target, final String name) throws Exception {
        Object obj = null;
        try {
            obj = getProperty(target, name);
        }
        catch (NoSuchFieldException ex) {
            final Field f = target.getClass().getSuperclass().getDeclaredField(name);
            f.setAccessible(true);
            obj = f.get(target);
        }
        return obj;
    }
    
    public static void setProperty(final Object target, final String name, final Object value) throws Exception {
        final Field f = target.getClass().getDeclaredField(name);
        f.setAccessible(true);
        f.set(target, value);
    }
    
    public static List<Field> getAllFields(final Class<?> clazz) throws Exception {
        return getAllFields(clazz, null, true);
    }
    
    public static List<Field> getAllFields(final Class<?> clazz, final boolean excludeStaticFileds) throws Exception {
        return getAllFields(clazz, null, excludeStaticFileds);
    }
    
    public static List<Field> getAllFields(final Class<?> clazz, final Class<? extends Annotation> annotation, final boolean excludeStaticFileds) throws Exception {
        if (clazz == null) {
            return null;
        }
        final List<Field> r = new LinkedList<Field>();
        for (Class<?> parent = clazz; parent != null; parent = parent.getSuperclass()) {
            for (final Field f : parent.getDeclaredFields()) {
                f.setAccessible(true);
                if (!excludeStaticFileds || (f.getModifiers() & 0x8) == 0x0) {
                    if (annotation == null || f.isAnnotationPresent(annotation)) {
                        r.add(f);
                    }
                }
            }
        }
        return r;
    }
    
    public static String getFieldName(final Method m) {
        if (m == null) {
            return null;
        }
        if (!isGetterMethod(m) && !isSetterMethod(m)) {
            return null;
        }
        final StringBuilder r = new StringBuilder();
        if (isIsMethod(m)) {
            r.append(m.getName().substring(2));
        }
        else if (isGetterMethod(m)) {
            r.append(m.getName().substring(3));
        }
        else if (isSetterMethod(m)) {
            r.append(m.getName().substring(3));
        }
        r.replace(0, 1, r.substring(0, 1).toLowerCase());
        return r.toString();
    }
    
    public static boolean isIsMethod(final Method method) {
        return method != null && method.getName().startsWith("is") && method.getParameterTypes().length == 0 && method.getReturnType().equals(Boolean.TYPE);
    }
    
    public static boolean isGetterMethod(final Method method) {
        return method != null && (isIsMethod(method) || (method.getName().startsWith("get") && method.getParameterTypes().length == 0 && !isVoid(method.getReturnType())));
    }
    
    public static boolean isSetterMethod(final Method method) {
        return method != null && method.getName().startsWith("set") && method.getParameterTypes().length == 1 && isVoid(method.getReturnType());
    }
    
    public static Method findGetterMethod(final Class<?> clazz, final Field field) {
        final StringBuilder sb = new StringBuilder(field.getName());
        sb.replace(0, 1, sb.substring(0, 1).toUpperCase());
        sb.insert(0, "get");
        Method r = findPublicMethod(clazz, sb.toString(), new Class[0]);
        if (r == null) {
            if (field.getType().equals(Boolean.TYPE)) {
                sb.replace(0, 3, "is");
            }
            r = findPublicMethod(clazz, sb.toString(), new Class[0]);
        }
        return r;
    }
    
    public static Method findSetterMethod(final Class<?> clazz, final Field field) {
        final StringBuilder sb = new StringBuilder(field.getName());
        sb.replace(0, 1, sb.substring(0, 1).toUpperCase());
        sb.insert(0, "set");
        Class<?> type = field.getType();
        if (type.isPrimitive()) {
            type = Classes.primitiveToClass(type);
        }
        return findPublicMethod(clazz, sb.toString(), new Class[] { type });
    }
    
    public static Method getPublicMethod(final Class<?> clazz, final String name, final Class<?>[] signatures) throws NoSuchMethodException {
        final Method r = findPublicMethod(clazz, name, signatures);
        if (r == null) {
            throw new NoSuchMethodException(clazz.getName() + "." + name + ArrayUtils.toString((Object)signatures));
        }
        return r;
    }
    
    public static Method findPublicMethod(final Class<?> clazz, final String name, final Class<?>[] signatures) {
        if (signatures.length == 0) {
            try {
                return clazz.getMethod(name, signatures);
            }
            catch (NoSuchMethodException e) {
                return null;
            }
            catch (SecurityException ex) {}
        }
        final List<Method> methods = new ArrayList<Method>();
        for (final Method method : clazz.getMethods()) {
            if (method.getName().equals(name) && matchArguments(signatures, method.getParameterTypes(), false)) {
                methods.add(method);
            }
        }
        if (methods.size() == 0) {
            return null;
        }
        if (methods.size() == 1) {
            return methods.get(0);
        }
        for (final Method method2 : methods) {
            if (matchArguments(signatures, method2.getParameterTypes(), true)) {
                return method2;
            }
        }
        return getMostSpecificMethod(methods, signatures);
    }
    
    private static Method getMostSpecificMethod(final List<Method> methods, final Class<?>[] signatures) {
        int maxMatches = 0;
        Method method = null;
        for (final Method m : methods) {
            int matches = 0;
            final Class<?>[] paramTypes = m.getParameterTypes();
            for (int i = 0; i < signatures.length; ++i) {
                Class<?> paramType = paramTypes[i];
                if (paramType.isPrimitive() && !signatures[i].isPrimitive()) {
                    paramType = Classes.primitiveToClass(paramType);
                }
                if (signatures[i] == paramType) {
                    ++matches;
                }
            }
            if (matches == 0 && maxMatches == 0) {
                if (method == null) {
                    method = m;
                }
                else {
                    if (matchArguments(method.getParameterTypes(), m.getParameterTypes(), false)) {
                        continue;
                    }
                    method = m;
                }
            }
            else if (matches > maxMatches) {
                maxMatches = matches;
                method = m;
            }
            else {
                if (matches != maxMatches) {
                    continue;
                }
                method = null;
            }
        }
        return method;
    }
    
    private static boolean matchArguments(final Class<?>[] signatures, final Class<?>[] paramTypes, final boolean explicit) {
        if (signatures.length != paramTypes.length) {
            return false;
        }
        for (int j = 0; j < signatures.length; ++j) {
            Class<?> paramType = paramTypes[j];
            if (paramType.isPrimitive() && !signatures[j].isPrimitive()) {
                paramType = Classes.primitiveToClass(paramType);
            }
            if (explicit) {
                if (signatures[j] != paramType) {
                    return false;
                }
            }
            else if (signatures[j] != null && !paramType.isAssignableFrom(signatures[j])) {
                return false;
            }
        }
        return true;
    }
}
